ELB ヘルスチェックのリクエストヘッダーの確認と、任意のエラーコードでヘルスチェックを失敗させたい
ターゲットグループのヘルスチェックについて調べる機会がありました。確認、検証した内容をまとめます。
この記事で学べること
- ヘルスチェックのリクエストヘッダーの確認方法
- 任意のエラーコードでヘルスチェックのエラーを出力方法
ヘルスチェックについて
ターゲットグループに登録されたターゲット(インスタンスや、コンテナ)に対して定期的にリクエストを送信し、期待した応答があるかどうかでターゲットの正常性を確認する動作です。
ヘルスチェックのリクエストヘッダー確認
ヘルスチェックのリクエストヘッダーを出力し確認する機会がありました。
nc
コマンドで確認しました。ターゲットグループのヘルスチェックパスは/health
でポートは8080
を指定しています。
ターゲットグループ設定値
項目 | 値 |
---|---|
ターゲットの種類 | インスタンス |
プロトコル:ポート | HTTP:8080 |
プロトコルバージョン | HTTP1 |
プロトコル | HTTP |
パス | /health |
ポート | traffic-port |
出力結果です。求めていたものです。
$ nc -l 8080 GET /health HTTP/1.1 Host: 10.0.17.184:8080 Connection: close User-Agent: ELB-HealthChecker/2.0 Accept-Encoding: gzip, compressed
簡単にリクエストヘッダーを出力できました。ヘッダーの説明は下記リンクをご確認ください。
nc
コマンドがあればサクッと確認できました。思いついた中では一番手っ取り早い方法でした。
Pythonはどう?
Python 2.xは標準でインストールされていることが多いのでサクッと確認できるのではないか?と思ったので試しました。
SimpleHTTPServerを使ったワンライナーのコマンドではリクエストヘッダーの取得はできませんでした。nc
コマンドと同等の手軽さを求めていたのでワンライナーにこだわってしまいました。
$ python -V Python 2.7.18 $ python -m SimpleHTTPServer 8080 Serving HTTP on 0.0.0.0 port 8080 ... 10.0.2.180 - - [20/Apr/2021 02:44:32] code 404, message File not found 10.0.2.180 - - [20/Apr/2021 02:44:32] "GET /health HTTP/1.1" 404 -
ワンライナーのHTTPサーバ探したらいろいろな方法が見つかり面白かったです。ご興味があればこちらもどうぞ。
ヘルスチェックのステータスを変えたい
任意のステータスコードでヘルスチェックをエラーにして確認したいことがありました。
nc
コマンドであればヘルスチェックパスを/
に設定すると簡単にできそうなので試してみました。
ステータスコードを200を返してヘルスチェックが正常の判定になるか確認します。
while true; do ( echo "HTTP/1.1 200 Ok"; echo; echo "I'm OK" ) | nc -l 8080; done
実行結果はヘルスチェックのリクエストのたびに下記の出力が繰り返されます。単発のnc -l 8080
コマンドと同じ出力結果です。
GET / HTTP/1.1 Host: 10.0.17.184:8080 Connection: close User-Agent: ELB-HealthChecker/2.0 Accept-Encoding: gzip, compressed
healthyを確認できました。ループしておけばヘルスチェックのテストに利用できることがわかりました。
ELBトラブルシュートのドキュメントを参考にステータスコードを460に指定しました。ステータスの内容が不明だったので適当にI don't know
を入れました
while true; do ( echo "HTTP/1.1 460 I don't know..."; echo; echo "I'm OK" ) | nc -l 8080; done
unhealthyになり、エラーコード460と確認できます。ステータスコードの数字を書き換えるだけで任意のエラーを起こせることがわかりました。
ヘルスチェックのパスを変更したい
nc
コマンドで/health
宛てのヘルスチェックを試したいときにパスの変更方法がわかりませんでした。
GoのWEBフレームワークEchoを使い、ビルドした実行ファイル一発でWEBサーバを起動して確認することにしました。
WEBサーバの準備
レスポンスヘッダーに設定するステータスコードを各々指定しています。便利なことにステータスコードは定数が用意されていました。
/health
にアクセスすると200 Ok
が返る/health2
は、405 Method not allowed
/health3
は、501 Not implemented
package main import ( "fmt" "log" "net/http" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) // Health check status type StatusResponse struct { Status int `json:"status"` } func main() { // Echo instance e := echo.New() // Middleware e.Use(middleware.Logger()) e.Use(middleware.Recover()) // Routes e.GET("/", Home) e.GET("/health", HealthCheck) e.GET("/health2", HealthCheck2) e.GET("/health3", HealthCheck3) // Start server e.Logger.Fatal(e.Start(":8080")) } // Handler func Home(c echo.Context) error { return c.String(http.StatusOK, "Hello Abashiri") } func HealthCheck(c echo.Context) error { headers := fmt.Sprintf("%v", c.Request().Header) log.Println("RequestHearders:", headers) r := &StatusResponse{ Status: http.StatusOK, } return c.JSON(http.StatusOK, r) } func HealthCheck2(c echo.Context) error { statusCode := http.StatusMethodNotAllowed r := &StatusResponse{ Status: statusCode, } return c.JSON(statusCode, r) } func HealthCheck3(c echo.Context) error { statusCode := http.StatusNotImplemented r := &StatusResponse{ Status: statusCode, } return c.JSON(statusCode, r) }
Arm(Graviton2)インスタンスで実行するためクロスコンパイルします。
$ go version go version go1.16.3 darwin/amd64 $ GOOS=linux GOARCH=arm64 go build -o responseCode main.go
EC2インスタンスへ実行ファイルをコピーして実行すると簡単にWEBサーバが起動しました。不要になったら実行ファイル削除するだけで済み後片付けも楽です。
$ ./responseCode ____ __ / __/___/ / ___ / _// __/ _ \/ _ \ /___/\__/_//_/\___/ v4.2.2 High performance, minimalist Go web framework https://echo.labstack.com ____________________________________O/_______ O\ ⇨ http server started on [::]:8080
/healthの結果
ヘルスチェックパスを/health
に変更しました。当然のようにhealthyを確認できます。
WEBサーバのログ
{"time":"2021-04-20T04:52:29.304548416Z","id":"","remote_ip":"10.0.2.180","host":"10.0.17.184:8080","method":"GET","uri":"/","user_agent":"ELB-HealthChecker/2.0","status":200,"error":"","latency":22531,"latency_human":"22.531µs","bytes_in":0,"bytes_out":14}
/health2の結果
ヘルスチェックパスを/health2
に変更しました。サーバのログからもリクエストしたパスを読み取れます。想定どおりunhealthyで任意のエラーを確認できました。
{"time":"2021-04-20T04:53:49.382389472Z","id":"","remote_ip":"10.0.2.180","host":"10.0.17.184:8080","method":"GET","uri":"/health2","user_agent":"ELB-HealthChecker/2.0","status":405,"error":"","latency":29325,"latency_human":"29.325µs","bytes_in":0,"bytes_out":15}
/health3の結果
同上です。
{"time":"2021-04-20T05:47:22.594833734Z","id":"","remote_ip":"10.0.2.180","host":"10.0.17.184:8080","method":"GET","uri":"/health3","user_agent":"ELB-HealthChecker/2.0","status":501,"error":"","latency":29235,"latency_human":"29.235µs","bytes_in":0,"bytes_out":15}
おわりに
リクエストヘッダーと、レスポンスのステータスコードと向き合いました。nc
コマンドが便利。後半はなにに役立つかわからない検証だったのですが調べているうちに新しいことを学びました。
ターゲットグループに有効なターゲットが存在せず、すべてヘルスチェックに失敗するとフェイルオープンの動作すること。
If all targets fail health checks at the same time in all enabled Availability Zones, the load balancer fails open. The effect of the fail open is to allow traffic to all targets in all enabled Availability Zones, regardless of their health status.
Health checks for your target groups - Elastic Load Balancing
「あー、認識誤ってたわ」と思ったらブログにまとまってました。なんでもあるので驚くばかりです、同じ会社なのですけど。